package com.label.Config.Auth.Shiro;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.label.Config.Common.Constant;
import com.label.Model.Common.JwtToken;
import com.label.Model.Do.Permission;
import com.label.Model.Do.Role;
import com.label.Model.Do.User;
import com.label.Model.Query.UserQuery;
import com.label.Service.UserService;
import com.label.Utils.Common.JwtUtil;
import com.label.Utils.Common.RedisUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 自定义Realm
 * @author dolyw.com
 * @date 2018/8/30 14:10
 */
@Service
public class UserRealm extends AuthorizingRealm {

    @Resource
    private UserService userService;

    @Override
    public boolean supports(AuthenticationToken authenticationToken) {
        return authenticationToken instanceof JwtToken;
    }

    /**
     * 只有当需要检测用户权限的时候才会调用此方法，例如checkRole,checkPermission之类的
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        String account = JwtUtil.getClaim(principalCollection.toString(), Constant.ACCOUNT);
        if (StringUtils.isBlank(account)) {
            throw new AuthenticationException("Token中帐号为空(The account in Token is empty.)");
        }
        User user=userService.getOneUserByAccount(new UserQuery(account));
        if(user==null)
            throw new AuthenticationException("该用户不存在");
        // 查询用户角色
        List<String> roles=user.getRoles().stream().map(Role::getName).collect(Collectors.toList());
        List<String> permissions=user.getRoles().stream().map(p->p.getPermissions().stream().map(Permission::getPerCode).collect(Collectors.toList())).collect(Collectors.toList()).stream().collect(ArrayList::new, ArrayList::addAll, ArrayList::addAll);
        simpleAuthorizationInfo.addRoles(roles);
        simpleAuthorizationInfo.addStringPermissions(permissions);

        return simpleAuthorizationInfo;
    }

    /**
     * 默认使用此方法进行用户名正确与否验证，错误抛出异常即可。
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            String token = (String) authenticationToken.getCredentials();
            String account = JwtUtil.getClaim(token, Constant.ACCOUNT);
            if (StringUtils.isBlank(account)) {
                throw new AuthenticationException("Token中帐号为空(The account in Token is empty.)");
            }
            // 查询用户是否存在
            User user=userService.getOne(new QueryWrapper<User>().lambda().eq(User::getAccount,account));
            if (user == null) {
                throw new AuthenticationException("该帐号不存在(The account does not exist.)");
            }
            // 开始认证，要AccessToken认证通过，且Redis中存在RefreshToken，且两个Token时间戳一致
            if (JwtUtil.verify(token) && RedisUtil.getObject(Constant.PREFIX_SHIRO_REFRESH_TOKEN + account)!=null) {
                // 获取RefreshToken的时间戳
                String currentTimeMillisRedis = RedisUtil.getObject(Constant.PREFIX_SHIRO_REFRESH_TOKEN + account).toString();
                // 获取AccessToken时间戳，与RefreshToken的时间戳对比
                if (JwtUtil.getClaim(token, Constant.CURRENT_TIME_MILLIS).equals(currentTimeMillisRedis)) {
                    return new SimpleAuthenticationInfo(token, token, "userRealm");
                }
            }
            throw new AuthenticationException("Token已过期");
    }
}
